package wangwenjun.phase1.reference.guavacache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import util.ThreadUtil;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

/**
 * @author ChangLiang
 * @date 2020/6/20
 */
@Slf4j
public class AppTest {

    private boolean isFromDb = false;

    @Test
    @DisplayName("test eviction by time")
    public void testEvictionByTime() throws ExecutionException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(10)
                .expireAfterAccess(10, TimeUnit.SECONDS)
                .build(new CacheLoader<String, Employee>() {
                    @Override
                    public Employee load(String key) throws Exception {
                        return findEmployeeByName(key);
                    }
                });
        Employee employee;
        employee = cache.get("Alex");
        assertLoadFromDbThenReset();

        employee = cache.get("Alex");
        assertLoadFromCacheThenReset();

        ThreadUtil.sleepForSeconds(11);
        employee = cache.get("Alex");
        assertLoadFromDbThenReset();
    }

    private void assertLoadFromCacheThenReset() {
        assertThat(isFromDb).isFalse();
        isFromDb = false;
    }

    private void assertLoadFromDbThenReset() {
        assertThat(isFromDb).isTrue();
        isFromDb = false;
    }

    @Test
    @DisplayName("test Eviction by Size")
    public void testEvictionBySize() {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .build(new CacheLoader<String, Employee>() {
                    @Override
                    public Employee load(String key) throws Exception {
                        return findEmployeeByName(key);
                    }
                });
        cache.getUnchecked("Alex");
        assertLoadFromDbThenReset();

        cache.getUnchecked("Jack");
        assertLoadFromDbThenReset();

        cache.getUnchecked("Gavin");
        assertLoadFromDbThenReset();

        assertThat(cache.size()).isEqualTo(3);

        cache.getUnchecked("Susan");

        assertThat(cache.getIfPresent("Alex")).isEqualTo(null);
        assertThat(cache.size()).isEqualTo(3);
    }

    @Test
    @DisplayName("test Eviction by weight")
    public void testEvictionByWeight() {
        Weigher<String, Employee> weigher = (key, employee) ->
                employee.getName().length() + employee.getEmpID().length() + employee.getDept().length();
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumWeight(45)
                /**
                 * 当要求有移除eviction算法时，访问队列和写队列在每个segment上保持。
                 * 因此，在编写单元测试时，通常会指定{@code concurrencyLevel（1）}以实现更具确定性的收回行为。
                 */
                .concurrencyLevel(1)
                .weigher(weigher)
                .build(new CacheLoader<String, Employee>() {
                    @Override
                    public Employee load(String key) throws Exception {
                        return findEmployeeByName(key);
                    }
                });
    }

    private Employee findEmployeeByName(final String name) {
        log.warn("The employee " + name + " is load from DB.");
        isFromDb = true;
        return new Employee(name, "dept", "empId");
    }
}
